package com.hzw.saas.web.sso.controller.sso;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.hzw.saas.common.config.annotation.SysLog;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.config.util.BearerTokenExtractor;
import com.hzw.saas.web.sso.controller.dto.SsoTokenDto;
import com.hzw.saas.web.sso.controller.param.LoginParam;
import com.hzw.saas.web.sso.controller.param.Oauth2Param;
import com.hzw.saas.web.sso.exception.UsernamePasswordErrorException;
import com.hzw.saas.web.sso.pojo.dto.CodeInfoDto;
import com.hzw.saas.web.sso.pojo.model.SaasClient;
import com.hzw.saas.web.sso.pojo.redis.SsoUser;
import com.hzw.saas.web.sso.service.ISaasClientService;
import com.hzw.saas.web.sso.service.ISaasOauthService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiOperationSort;
import io.swagger.annotations.ApiSort;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Objects;

/**
 * 二、目前实现方案
 * 假设 目前有3个网站，其中1个认证中心(sso.com)，2个应用网站(a.com,b.com)
 * 场景用户输入用户名和密码登录a.com, 即可自动登录b.com，怎么实现呢？
 * 用户登录a.com，我们会跳转sso.com，并带上a.com的回调地址，如：http://sso.com?redirect=a.com/login
 * 在sso.com判断是否已登录，如未登录则出现登录界面
 * 登录成功后，生成一个授权Token，保存在http://sso.com
 * 随后，生成code(授权码)，sso.com立即跳转到http://a.com/login?code=code
 * 最后，应用拿着自己的client_id以及client_secret以及接收到的授权码，请求token
 * @author sonam
 * @sine 2021/11/9 3:25 下午
 */
@Slf4j
@RestController
@ApiSort(value = 1)
@RequiredArgsConstructor
@Api(tags = "鸿之微/OAuth2认证内部接口")
@RequestMapping("/saas/oauth2")
public class SaasOauthLoginController {

    private final ISaasOauthService oauthService;
    private final PasswordEncoder passwordEncoder;
    private final ISaasClientService clientService;
    private final BearerTokenExtractor tokenExtractor;
    private final UserDetailsService userDetailsService;

    @SysLog
    @PostMapping("/check")
    @ApiOperationSort(value = 1)
    @ApiOperation(value = "校验client参数")
    public ResponseEntity<Object> checkClient(@RequestBody Oauth2Param oauth2Param) {
        // 校验client参数是否合法
        SaasClient client = clientService.getById(oauth2Param.getClientId());
        oauthService.check(client,
            oauth2Param.getRedirectUri(),
            CollectionUtil.isEmpty(oauth2Param.getScopes()) ? Collections.singletonList("all") : oauth2Param.getScopes(),
            StrUtil.isBlank(oauth2Param.getResponseType()) ? "code" : oauth2Param.getResponseType());
        return ResponseEntity.ok(client.getName());
    }

    @SysLog
    @PostMapping("/login")
    @ApiOperation(value = "SSO登录")
    @ApiOperationSort(value = 2)
    public ResponseEntity<SsoTokenDto> login(@Validated @RequestBody LoginParam login) {
        UserDetails user = userDetailsService.loadUserByUsername(login.getUsername());
        if(!passwordEncoder.matches(login.getPassword(), user.getPassword())) {
            throw new UsernamePasswordErrorException("用户或密码不正确");
        }
        SsoTokenDto tokenDto = oauthService.buildSsoToken(user, login.getPassword());
        return ResponseEntity.ok(tokenDto);
    }

    @PostMapping("/isLogin")
    @ApiOperationSort(value = 3)
    @ApiOperation(value = "检查是否登录", notes = "请求头中带 'Authorization': 'Bearer Token'")
    public ResponseEntity<Object> isLogin(HttpServletRequest request) {
        AssertUtil.assertThrow("未登录", HttpStatus.UNAUTHORIZED, Objects.isNull(getUser(request)));
        return ResponseEntity.ok().build();
    }

    @PostMapping("/code")
    @ApiOperationSort(value = 4)
    @ApiOperation(value = "获取授权码")
    public ResponseEntity<Object> getCode(@RequestBody Oauth2Param oauth2Param, HttpServletRequest request) {
        SsoUser user = getUser(request);
        AssertUtil.assertThrow("未登录", HttpStatus.UNAUTHORIZED, Objects.isNull(getUser(request)));
        CodeInfoDto code = oauthService.getCode(clientService.getById(oauth2Param.getClientId()),
            CollectionUtil.isEmpty(oauth2Param.getScopes()) ? Collections.singletonList("all") : oauth2Param.getScopes(),
            oauth2Param.getRedirectUri(),
            user.getUserDetails().getUsername(), user.getPassword());
        return ResponseEntity.ok(code.getRedirectUrl());
    }

    private SsoUser getUser(HttpServletRequest request) {
        String token = tokenExtractor.extract(request);
        if(Objects.isNull(token)) {
            return null;
        }
        return oauthService.getSsoTokenInfo(token);
    }

}
